OpenTelemetry Instrumentation for web and node
Note: This is an experimental package under active development. New releases may include breaking changes.
Installation
Note: Much of OpenTelemetry JS documentation is written assuming the compiled application is run as CommonJS.
For more details on ECMAScript Modules vs CommonJS, refer to esm-support.
npm install --save @opentelemetry/instrumentation
Usage in Node
import {
InstrumentationBase,
InstrumentationConfig,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
} from '@opentelemetry/instrumentation';
import type * as module_name_to_be_patched from 'module_name_to_be_patched';
export class MyInstrumentation extends InstrumentationBase {
constructor(config: InstrumentationConfig = {}) {
super('MyInstrumentation', VERSION, config);
}
protected init() {
const module = new InstrumentationNodeModuleDefinition(
'module_name_to_be_patched',
['1.*'],
this._onPatchMain,
this._onUnPatchMain,
);
module.files.push(this._addPatchingMethod());
return module;
}
private _onPatchMain(moduleExports: typeof module_name_to_be_patched) {
this._wrap(
moduleExports,
'mainMethodName',
this._patchMainMethodName()
);
return moduleExports;
}
private _onUnPatchMain(moduleExports: typeof module_name_to_be_patched) {
this._unwrap(moduleExports, 'mainMethodName');
}
private _addPatchingMethod(): InstrumentationNodeModuleFile {
const file = new InstrumentationNodeModuleFile(
'module_name_to_be_patched/src/some_file.js',
this._onPatchMethodName,
this._onUnPatchMethodName,
);
return file;
}
private _onPatchMethodName(moduleExports: typeof module_name_to_be_patched) {
this._wrap(
moduleExports,
'methodName',
this._patchMethodName()
);
return moduleExports;
}
private _onUnPatchMethodName(moduleExports: typeof module_name_to_be_patched) {
this._unwrap(moduleExports, 'methodName');
}
private _patchMethodName(): (original) => any {
const plugin = this;
return function methodName(original) {
return function patchMethodName(this: any): PromiseOrValue<module_name_to_be_patched.methodName> {
console.log('methodName', arguments);
return original.apply(this, arguments);
};
};
}
private _patchMainMethodName(): (original) => any {
const plugin = this;
return function mainMethodName(original) {
return function patchMainMethodName(this: any): PromiseOrValue<module_name_to_be_patched.mainMethodName> {
console.log('mainMethodName', arguments);
return original.apply(this, arguments);
};
};
}
}
const myInstrumentation = new MyInstrumentation();
myInstrumentation.setTracerProvider(provider);
myInstrumentation.setMeterProvider(meterProvider);
myInstrumentation.enable();
Usage in Web
import {
InstrumentationBase,
InstrumentationConfig,
} from '@opentelemetry/instrumentation';
import { Instrumentation } from '@opentelemetry/instrumentation';
export class MyInstrumentation extends InstrumentationBase {
constructor(config: InstrumentationConfig = {}) {
super('MyInstrumentation', VERSION, config);
}
private _patchOpen() {
return (original: OpenFunction): OpenFunction => {
const plugin = this;
return function patchOpen(this: XMLHttpRequest, ...args): void {
console.log('open', arguments);
return original.apply(this, args);
};
};
}
public enable() {
this._wrap(XMLHttpRequest.prototype, 'open', this._patchOpen());
}
public disable() {
this._unwrap(XMLHttpRequest.prototype, 'open');
}
}
const myInstrumentation = new MyInstrumentation();
myInstrumentation.setTracerProvider(provider);
myInstrumentation.setMeterProvider(meterProvider);
myInstrumentation.enable();
AutoLoader
NODE - Auto Loader
const { B3Propagator } = require('@opentelemetry/propagator-b3');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const tracerProvider = new NodeTracerProvider();
tracerProvider.register({
propagator: new B3Propagator(),
});
registerInstrumentations({
instrumentations: [
new HttpInstrumentation(),
],
});
WEB - Auto Loader
const { B3Propagator } = require('@opentelemetry/propagator-b3');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { XMLHttpRequestInstrumentation } = require('@opentelemetry/instrumentation-xml-http-request');
const { WebTracerProvider } = require('@opentelemetry/sdk-trace-web');
const tracerProvider = new WebTracerProvider();
tracerProvider.register({
propagator: new B3Propagator(),
});
registerInstrumentations({
instrumentations: [
new XMLHttpRequestInstrumentation({
ignoreUrls: [/localhost/],
propagateTraceHeaderCorsUrls: [
'http://localhost:8090',
],
}),
],
});
Selection of the used TracerProvider/MeterProvider
The registerInstrumentations()
API allows to specify which TracerProvider
and/or MeterProvider
to use by the given options object.
If nothing is specified the global registered provider is used. Usually this is what most users want therefore it's recommended to keep this default.
There might be use case where someone has the need for more providers within an application. Please note that special care must be takes in such setups
to avoid leaking information from one provider to the other because there are a lot places where e.g. the global ContextManager
or Propagator
is used.
Instrumentation for ECMAScript Modules (ESM) in Node.js (experimental)
Node.js uses a different module loader for ECMAScript Modules (ESM) vs. CommonJS (CJS).
A require()
call will cause Node.js to use the CommonJS module loader.
An import ...
statement or import()
call will cause Node.js to use the ECMAScript module loader.
If your application is written in JavaScript as ESM, or it must compile to ESM from TypeScript, then a loader hook is required to properly patch instrumentation.
The custom hook for ESM instrumentation is --experimental-loader=@opentelemetry/instrumentation/hook.mjs
.
This flag must be passed to the node
binary, which is often done as a startup command and/or in the NODE_OPTIONS
environment variable.
For more details on ECMAScript Modules vs CommonJS, refer to esm-support.
Limitations
Instrumentations for external modules (e.g. express, mongodb,...) hooks the require
call or import
statement. Therefore following conditions need to be met that this mechanism can work:
- Instrumentations are registered before the module to instrument is
require
ed (CJS only) - modules are not included in a bundle. Tools like
esbuild
, webpack
, ... usually have some mechanism to exclude specific modules from bundling
License
Apache 2.0 - See LICENSE for more information.
Useful links